// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/http/http_auth_gssapi_posix.h"

#include <limits>
#include <string>

#include "base/base64.h"
#include "base/files/file_path.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "net/base/net_errors.h"
#include "net/http/http_auth_multi_round_parse.h"

// These are defined for the GSSAPI library:
// Paraphrasing the comments from gssapi.h:
// "The implementation must reserve static storage for a
// gss_OID_desc object for each constant.  That constant
// should be initialized to point to that gss_OID_desc."
// These are encoded using ASN.1 BER encoding.
namespace {

static gss_OID_desc GSS_C_NT_USER_NAME_VAL = {
    10,
    const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01")
};
static gss_OID_desc GSS_C_NT_MACHINE_UID_NAME_VAL = {
    10,
    const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02")
};
static gss_OID_desc GSS_C_NT_STRING_UID_NAME_VAL = {
    10,
    const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03")
};
static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_X_VAL = {
    6,
    const_cast<char*>("\x2b\x06\x01\x05\x06\x02")
};
static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_VAL = {
    10,
    const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04")
};
static gss_OID_desc GSS_C_NT_ANONYMOUS_VAL = {
    6,
    const_cast<char*>("\x2b\x06\01\x05\x06\x03")
};
static gss_OID_desc GSS_C_NT_EXPORT_NAME_VAL = {
    6,
    const_cast<char*>("\x2b\x06\x01\x05\x06\x04")
};

} // namespace

// Heimdal >= 1.4 will define the following as preprocessor macros.
// To avoid conflicting declarations, we have to undefine these.
#undef GSS_C_NT_USER_NAME
#undef GSS_C_NT_MACHINE_UID_NAME
#undef GSS_C_NT_STRING_UID_NAME
#undef GSS_C_NT_HOSTBASED_SERVICE_X
#undef GSS_C_NT_HOSTBASED_SERVICE
#undef GSS_C_NT_ANONYMOUS
#undef GSS_C_NT_EXPORT_NAME

gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_VAL;
gss_OID GSS_C_NT_MACHINE_UID_NAME = &GSS_C_NT_MACHINE_UID_NAME_VAL;
gss_OID GSS_C_NT_STRING_UID_NAME = &GSS_C_NT_STRING_UID_NAME_VAL;
gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = &GSS_C_NT_HOSTBASED_SERVICE_X_VAL;
gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_VAL;
gss_OID GSS_C_NT_ANONYMOUS = &GSS_C_NT_ANONYMOUS_VAL;
gss_OID GSS_C_NT_EXPORT_NAME = &GSS_C_NT_EXPORT_NAME_VAL;

namespace net {

// Exported mechanism for GSSAPI. We always use SPNEGO:

// iso.org.dod.internet.security.mechanism.snego (1.3.6.1.5.5.2)
gss_OID_desc CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL = {
    6,
    const_cast<char*>("\x2b\x06\x01\x05\x05\x02")
};

gss_OID CHROME_GSS_SPNEGO_MECH_OID_DESC = &CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL;

// Debugging helpers.
namespace {

    std::string DisplayStatus(OM_uint32 major_status,
        OM_uint32 minor_status)
    {
        if (major_status == GSS_S_COMPLETE)
            return "OK";
        return base::StringPrintf("0x%08X 0x%08X", major_status, minor_status);
    }

    std::string DisplayCode(GSSAPILibrary* gssapi_lib,
        OM_uint32 status,
        OM_uint32 status_code_type)
    {
        const int kMaxDisplayIterations = 8;
        const size_t kMaxMsgLength = 4096;
        // msg_ctx needs to be outside the loop because it is invoked multiple times.
        OM_uint32 msg_ctx = 0;
        std::string rv = base::StringPrintf("(0x%08X)", status);

        // This loop should continue iterating until msg_ctx is 0 after the first
        // iteration. To be cautious and prevent an infinite loop, it stops after
        // a finite number of iterations as well. As an added sanity check, no
        // individual message may exceed |kMaxMsgLength|, and the final result
        // will not exceed |kMaxMsgLength|*2-1.
        for (int i = 0; i < kMaxDisplayIterations && rv.size() < kMaxMsgLength;
             ++i) {
            OM_uint32 min_stat;
            gss_buffer_desc_struct msg = GSS_C_EMPTY_BUFFER;
            OM_uint32 maj_stat = gssapi_lib->display_status(&min_stat, status, status_code_type,
                GSS_C_NULL_OID, &msg_ctx, &msg);
            if (maj_stat == GSS_S_COMPLETE) {
                int msg_len = (msg.length > kMaxMsgLength) ? static_cast<int>(kMaxMsgLength) : static_cast<int>(msg.length);
                if (msg_len > 0 && msg.value != NULL) {
                    rv += base::StringPrintf(" %.*s", msg_len,
                        static_cast<char*>(msg.value));
                }
            }
            gssapi_lib->release_buffer(&min_stat, &msg);
            if (!msg_ctx)
                break;
        }
        return rv;
    }

    std::string DisplayExtendedStatus(GSSAPILibrary* gssapi_lib,
        OM_uint32 major_status,
        OM_uint32 minor_status)
    {
        if (major_status == GSS_S_COMPLETE)
            return "OK";
        std::string major = DisplayCode(gssapi_lib, major_status, GSS_C_GSS_CODE);
        std::string minor = DisplayCode(gssapi_lib, minor_status, GSS_C_MECH_CODE);
        return base::StringPrintf("Major: %s | Minor: %s", major.c_str(),
            minor.c_str());
    }

    // ScopedName releases a gss_name_t when it goes out of scope.
    class ScopedName {
    public:
        ScopedName(gss_name_t name,
            GSSAPILibrary* gssapi_lib)
            : name_(name)
            , gssapi_lib_(gssapi_lib)
        {
            DCHECK(gssapi_lib_);
        }

        ~ScopedName()
        {
            if (name_ != GSS_C_NO_NAME) {
                OM_uint32 minor_status = 0;
                OM_uint32 major_status = gssapi_lib_->release_name(&minor_status, &name_);
                if (major_status != GSS_S_COMPLETE) {
                    LOG(WARNING) << "Problem releasing name. "
                                 << DisplayStatus(major_status, minor_status);
                }
                name_ = GSS_C_NO_NAME;
            }
        }

    private:
        gss_name_t name_;
        GSSAPILibrary* gssapi_lib_;

        DISALLOW_COPY_AND_ASSIGN(ScopedName);
    };

    // ScopedBuffer releases a gss_buffer_t when it goes out of scope.
    class ScopedBuffer {
    public:
        ScopedBuffer(gss_buffer_t buffer,
            GSSAPILibrary* gssapi_lib)
            : buffer_(buffer)
            , gssapi_lib_(gssapi_lib)
        {
            DCHECK(gssapi_lib_);
        }

        ~ScopedBuffer()
        {
            if (buffer_ != GSS_C_NO_BUFFER) {
                OM_uint32 minor_status = 0;
                OM_uint32 major_status = gssapi_lib_->release_buffer(&minor_status, buffer_);
                if (major_status != GSS_S_COMPLETE) {
                    LOG(WARNING) << "Problem releasing buffer. "
                                 << DisplayStatus(major_status, minor_status);
                }
                buffer_ = GSS_C_NO_BUFFER;
            }
        }

    private:
        gss_buffer_t buffer_;
        GSSAPILibrary* gssapi_lib_;

        DISALLOW_COPY_AND_ASSIGN(ScopedBuffer);
    };

    namespace {

        std::string AppendIfPredefinedValue(gss_OID oid,
            gss_OID predefined_oid,
            const char* predefined_oid_name)
        {
            DCHECK(oid);
            DCHECK(predefined_oid);
            DCHECK(predefined_oid_name);
            std::string output;
            if (oid->length != predefined_oid->length)
                return output;
            if (0 != memcmp(oid->elements, predefined_oid->elements, predefined_oid->length))
                return output;

            output += " (";
            output += predefined_oid_name;
            output += ")";
            return output;
        }

    } // namespace

    std::string DescribeOid(GSSAPILibrary* gssapi_lib, const gss_OID oid)
    {
        if (!oid)
            return "<NULL>";
        std::string output;
        const size_t kMaxCharsToPrint = 1024;
        OM_uint32 byte_length = oid->length;
        size_t char_length = byte_length / sizeof(char);
        if (char_length > kMaxCharsToPrint) {
            // This might be a plain ASCII string.
            // Check if the first |kMaxCharsToPrint| characters
            // contain only printable characters and are NULL terminated.
            const char* str = reinterpret_cast<const char*>(oid);
            size_t str_length = 0;
            for (; str_length < kMaxCharsToPrint; ++str_length) {
                if (!str[str_length] || !isprint(str[str_length]))
                    break;
            }
            if (!str[str_length]) {
                output += base::StringPrintf("\"%s\"", str);
                return output;
            }
        }
        output = base::StringPrintf("(%u) \"", byte_length);
        if (!oid->elements) {
            output += "<NULL>";
            return output;
        }
        const unsigned char* elements = reinterpret_cast<const unsigned char*>(oid->elements);
        // Don't print more than |kMaxCharsToPrint| characters.
        size_t i = 0;
        for (; (i < byte_length) && (i < kMaxCharsToPrint); ++i) {
            output += base::StringPrintf("\\x%02X", elements[i]);
        }
        if (i >= kMaxCharsToPrint)
            output += "...";
        output += "\"";

        // Check if the OID is one of the predefined values.
        output += AppendIfPredefinedValue(oid,
            GSS_C_NT_USER_NAME,
            "GSS_C_NT_USER_NAME");
        output += AppendIfPredefinedValue(oid,
            GSS_C_NT_MACHINE_UID_NAME,
            "GSS_C_NT_MACHINE_UID_NAME");
        output += AppendIfPredefinedValue(oid,
            GSS_C_NT_STRING_UID_NAME,
            "GSS_C_NT_STRING_UID_NAME");
        output += AppendIfPredefinedValue(oid,
            GSS_C_NT_HOSTBASED_SERVICE_X,
            "GSS_C_NT_HOSTBASED_SERVICE_X");
        output += AppendIfPredefinedValue(oid,
            GSS_C_NT_HOSTBASED_SERVICE,
            "GSS_C_NT_HOSTBASED_SERVICE");
        output += AppendIfPredefinedValue(oid,
            GSS_C_NT_ANONYMOUS,
            "GSS_C_NT_ANONYMOUS");
        output += AppendIfPredefinedValue(oid,
            GSS_C_NT_EXPORT_NAME,
            "GSS_C_NT_EXPORT_NAME");

        return output;
    }

    std::string DescribeName(GSSAPILibrary* gssapi_lib, const gss_name_t name)
    {
        OM_uint32 major_status = 0;
        OM_uint32 minor_status = 0;
        gss_buffer_desc_struct output_name_buffer = GSS_C_EMPTY_BUFFER;
        gss_OID_desc output_name_type_desc = GSS_C_EMPTY_BUFFER;
        gss_OID output_name_type = &output_name_type_desc;
        major_status = gssapi_lib->display_name(&minor_status,
            name,
            &output_name_buffer,
            &output_name_type);
        ScopedBuffer scoped_output_name(&output_name_buffer, gssapi_lib);
        if (major_status != GSS_S_COMPLETE) {
            std::string error = base::StringPrintf("Unable to describe name 0x%p, %s",
                name,
                DisplayExtendedStatus(gssapi_lib,
                    major_status,
                    minor_status)
                    .c_str());
            return error;
        }
        int len = output_name_buffer.length;
        std::string description = base::StringPrintf(
            "%*s (Type %s)",
            len,
            reinterpret_cast<const char*>(output_name_buffer.value),
            DescribeOid(gssapi_lib, output_name_type).c_str());
        return description;
    }

    std::string DescribeContext(GSSAPILibrary* gssapi_lib,
        const gss_ctx_id_t context_handle)
    {
        OM_uint32 major_status = 0;
        OM_uint32 minor_status = 0;
        gss_name_t src_name = GSS_C_NO_NAME;
        gss_name_t targ_name = GSS_C_NO_NAME;
        OM_uint32 lifetime_rec = 0;
        gss_OID mech_type = GSS_C_NO_OID;
        OM_uint32 ctx_flags = 0;
        int locally_initiated = 0;
        int open = 0;
        if (context_handle == GSS_C_NO_CONTEXT)
            return std::string("Context: GSS_C_NO_CONTEXT");
        major_status = gssapi_lib->inquire_context(&minor_status,
            context_handle,
            &src_name,
            &targ_name,
            &lifetime_rec,
            &mech_type,
            &ctx_flags,
            &locally_initiated,
            &open);
        ScopedName scoped_src_name(src_name, gssapi_lib);
        ScopedName scoped_targ_name(targ_name, gssapi_lib);
        if (major_status != GSS_S_COMPLETE) {
            std::string error = base::StringPrintf("Unable to describe context 0x%p, %s",
                context_handle,
                DisplayExtendedStatus(gssapi_lib,
                    major_status,
                    minor_status)
                    .c_str());
            return error;
        }
        std::string source(DescribeName(gssapi_lib, src_name));
        std::string target(DescribeName(gssapi_lib, targ_name));
        std::string description = base::StringPrintf("Context 0x%p: "
                                                     "Source \"%s\", "
                                                     "Target \"%s\", "
                                                     "lifetime %d, "
                                                     "mechanism %s, "
                                                     "flags 0x%08X, "
                                                     "local %d, "
                                                     "open %d",
            context_handle,
            source.c_str(),
            target.c_str(),
            lifetime_rec,
            DescribeOid(gssapi_lib,
                mech_type)
                .c_str(),
            ctx_flags,
            locally_initiated,
            open);
        return description;
    }

} // namespace

GSSAPISharedLibrary::GSSAPISharedLibrary(const std::string& gssapi_library_name)
    : initialized_(false)
    , gssapi_library_name_(gssapi_library_name)
    , gssapi_library_(NULL)
    , import_name_(NULL)
    , release_name_(NULL)
    , release_buffer_(NULL)
    , display_name_(NULL)
    , display_status_(NULL)
    , init_sec_context_(NULL)
    , wrap_size_limit_(NULL)
    , delete_sec_context_(NULL)
    , inquire_context_(NULL)
{
}

GSSAPISharedLibrary::~GSSAPISharedLibrary()
{
    if (gssapi_library_) {
        base::UnloadNativeLibrary(gssapi_library_);
        gssapi_library_ = NULL;
    }
}

bool GSSAPISharedLibrary::Init()
{
    if (!initialized_)
        InitImpl();
    return initialized_;
}

bool GSSAPISharedLibrary::InitImpl()
{
    DCHECK(!initialized_);
#if defined(DLOPEN_KERBEROS)
    gssapi_library_ = LoadSharedLibrary();
    if (gssapi_library_ == NULL)
        return false;
#endif // defined(DLOPEN_KERBEROS)
    initialized_ = true;
    return true;
}

base::NativeLibrary GSSAPISharedLibrary::LoadSharedLibrary()
{
    const char* const* library_names;
    size_t num_lib_names;
    const char* user_specified_library[1];
    if (!gssapi_library_name_.empty()) {
        user_specified_library[0] = gssapi_library_name_.c_str();
        library_names = user_specified_library;
        num_lib_names = 1;
    } else {
        static const char* const kDefaultLibraryNames[] = {
#if defined(OS_MACOSX)
            "/System/Library/Frameworks/Kerberos.framework/Kerberos"
#elif defined(OS_OPENBSD)
            "libgssapi.so" // Heimdal - OpenBSD
#else
            "libgssapi_krb5.so.2", // MIT Kerberos - FC, Suse10, Debian
            "libgssapi.so.4", // Heimdal - Suse10, MDK
            "libgssapi.so.2", // Heimdal - Gentoo
            "libgssapi.so.1" // Heimdal - Suse9, CITI - FC, MDK, Suse10
#endif
        };
        library_names = kDefaultLibraryNames;
        num_lib_names = arraysize(kDefaultLibraryNames);
    }

    for (size_t i = 0; i < num_lib_names; ++i) {
        const char* library_name = library_names[i];
        base::FilePath file_path(library_name);

        // TODO(asanka): Move library loading to a separate thread.
        //               http://crbug.com/66702
        base::ThreadRestrictions::ScopedAllowIO allow_io_temporarily;
        base::NativeLibraryLoadError load_error;
        base::NativeLibrary lib = base::LoadNativeLibrary(file_path, &load_error);
        if (lib) {
            // Only return this library if we can bind the functions we need.
            if (BindMethods(lib))
                return lib;
            base::UnloadNativeLibrary(lib);
        } else {
            // If this is the only library available, log the reason for failure.
            LOG_IF(WARNING, num_lib_names == 1) << load_error.ToString();
        }
    }
    LOG(WARNING) << "Unable to find a compatible GSSAPI library";
    return NULL;
}

#if defined(DLOPEN_KERBEROS)
#define BIND(lib, x)                                                \
    DCHECK(lib);                                                    \
    gss_##x##_type x = reinterpret_cast<gss_##x##_type>(            \
        base::GetFunctionPointerFromNativeLibrary(lib, "gss_" #x)); \
    if (x == NULL) {                                                \
        LOG(WARNING) << "Unable to bind function \""                \
                     << "gss_" #x << "\"";                          \
        return false;                                               \
    }
#else
#define BIND(lib, x) gss_##x##_type x = gss_##x
#endif

bool GSSAPISharedLibrary::BindMethods(base::NativeLibrary lib)
{
    BIND(lib, import_name);
    BIND(lib, release_name);
    BIND(lib, release_buffer);
    BIND(lib, display_name);
    BIND(lib, display_status);
    BIND(lib, init_sec_context);
    BIND(lib, wrap_size_limit);
    BIND(lib, delete_sec_context);
    BIND(lib, inquire_context);

    import_name_ = import_name;
    release_name_ = release_name;
    release_buffer_ = release_buffer;
    display_name_ = display_name;
    display_status_ = display_status;
    init_sec_context_ = init_sec_context;
    wrap_size_limit_ = wrap_size_limit;
    delete_sec_context_ = delete_sec_context;
    inquire_context_ = inquire_context;

    return true;
}

#undef BIND

OM_uint32 GSSAPISharedLibrary::import_name(
    OM_uint32* minor_status,
    const gss_buffer_t input_name_buffer,
    const gss_OID input_name_type,
    gss_name_t* output_name)
{
    DCHECK(initialized_);
    return import_name_(minor_status, input_name_buffer, input_name_type,
        output_name);
}

OM_uint32 GSSAPISharedLibrary::release_name(
    OM_uint32* minor_status,
    gss_name_t* input_name)
{
    DCHECK(initialized_);
    return release_name_(minor_status, input_name);
}

OM_uint32 GSSAPISharedLibrary::release_buffer(
    OM_uint32* minor_status,
    gss_buffer_t buffer)
{
    DCHECK(initialized_);
    return release_buffer_(minor_status, buffer);
}

OM_uint32 GSSAPISharedLibrary::display_name(
    OM_uint32* minor_status,
    const gss_name_t input_name,
    gss_buffer_t output_name_buffer,
    gss_OID* output_name_type)
{
    DCHECK(initialized_);
    return display_name_(minor_status,
        input_name,
        output_name_buffer,
        output_name_type);
}

OM_uint32 GSSAPISharedLibrary::display_status(
    OM_uint32* minor_status,
    OM_uint32 status_value,
    int status_type,
    const gss_OID mech_type,
    OM_uint32* message_context,
    gss_buffer_t status_string)
{
    DCHECK(initialized_);
    return display_status_(minor_status, status_value, status_type, mech_type,
        message_context, status_string);
}

OM_uint32 GSSAPISharedLibrary::init_sec_context(
    OM_uint32* minor_status,
    const gss_cred_id_t initiator_cred_handle,
    gss_ctx_id_t* context_handle,
    const gss_name_t target_name,
    const gss_OID mech_type,
    OM_uint32 req_flags,
    OM_uint32 time_req,
    const gss_channel_bindings_t input_chan_bindings,
    const gss_buffer_t input_token,
    gss_OID* actual_mech_type,
    gss_buffer_t output_token,
    OM_uint32* ret_flags,
    OM_uint32* time_rec)
{
    DCHECK(initialized_);
    return init_sec_context_(minor_status,
        initiator_cred_handle,
        context_handle,
        target_name,
        mech_type,
        req_flags,
        time_req,
        input_chan_bindings,
        input_token,
        actual_mech_type,
        output_token,
        ret_flags,
        time_rec);
}

OM_uint32 GSSAPISharedLibrary::wrap_size_limit(
    OM_uint32* minor_status,
    const gss_ctx_id_t context_handle,
    int conf_req_flag,
    gss_qop_t qop_req,
    OM_uint32 req_output_size,
    OM_uint32* max_input_size)
{
    DCHECK(initialized_);
    return wrap_size_limit_(minor_status,
        context_handle,
        conf_req_flag,
        qop_req,
        req_output_size,
        max_input_size);
}

OM_uint32 GSSAPISharedLibrary::delete_sec_context(
    OM_uint32* minor_status,
    gss_ctx_id_t* context_handle,
    gss_buffer_t output_token)
{
    // This is called from the owner class' destructor, even if
    // Init() is not called, so we can't assume |initialized_|
    // is set.
    if (!initialized_)
        return 0;
    return delete_sec_context_(minor_status,
        context_handle,
        output_token);
}

OM_uint32 GSSAPISharedLibrary::inquire_context(
    OM_uint32* minor_status,
    const gss_ctx_id_t context_handle,
    gss_name_t* src_name,
    gss_name_t* targ_name,
    OM_uint32* lifetime_rec,
    gss_OID* mech_type,
    OM_uint32* ctx_flags,
    int* locally_initiated,
    int* open)
{
    DCHECK(initialized_);
    return inquire_context_(minor_status,
        context_handle,
        src_name,
        targ_name,
        lifetime_rec,
        mech_type,
        ctx_flags,
        locally_initiated,
        open);
}

ScopedSecurityContext::ScopedSecurityContext(GSSAPILibrary* gssapi_lib)
    : security_context_(GSS_C_NO_CONTEXT)
    , gssapi_lib_(gssapi_lib)
{
    DCHECK(gssapi_lib_);
}

ScopedSecurityContext::~ScopedSecurityContext()
{
    if (security_context_ != GSS_C_NO_CONTEXT) {
        gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
        OM_uint32 minor_status = 0;
        OM_uint32 major_status = gssapi_lib_->delete_sec_context(
            &minor_status, &security_context_, &output_token);
        if (major_status != GSS_S_COMPLETE) {
            LOG(WARNING) << "Problem releasing security_context. "
                         << DisplayStatus(major_status, minor_status);
        }
        security_context_ = GSS_C_NO_CONTEXT;
    }
}

HttpAuthGSSAPI::HttpAuthGSSAPI(GSSAPILibrary* library,
    const std::string& scheme,
    gss_OID gss_oid)
    : scheme_(scheme)
    , gss_oid_(gss_oid)
    , library_(library)
    , scoped_sec_context_(library)
    , can_delegate_(false)
{
    DCHECK(library_);
}

HttpAuthGSSAPI::~HttpAuthGSSAPI()
{
}

bool HttpAuthGSSAPI::Init()
{
    if (!library_)
        return false;
    return library_->Init();
}

bool HttpAuthGSSAPI::NeedsIdentity() const
{
    return decoded_server_auth_token_.empty();
}

bool HttpAuthGSSAPI::AllowsExplicitCredentials() const
{
    return false;
}

void HttpAuthGSSAPI::Delegate()
{
    can_delegate_ = true;
}

HttpAuth::AuthorizationResult HttpAuthGSSAPI::ParseChallenge(
    HttpAuthChallengeTokenizer* tok)
{
    if (scoped_sec_context_.get() == GSS_C_NO_CONTEXT) {
        return net::ParseFirstRoundChallenge(scheme_, tok);
    }
    std::string encoded_auth_token;
    return net::ParseLaterRoundChallenge(scheme_, tok, &encoded_auth_token,
        &decoded_server_auth_token_);
}

int HttpAuthGSSAPI::GenerateAuthToken(const AuthCredentials* credentials,
    const std::string& spn,
    const std::string& channel_bindings,
    std::string* auth_token,
    const CompletionCallback& /*callback*/)
{
    DCHECK(auth_token);

    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
    input_token.length = decoded_server_auth_token_.length();
    input_token.value = (input_token.length > 0) ? const_cast<char*>(decoded_server_auth_token_.data()) : NULL;
    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
    ScopedBuffer scoped_output_token(&output_token, library_);
    int rv = GetNextSecurityToken(spn, channel_bindings, &input_token, &output_token);
    if (rv != OK)
        return rv;

    // Base64 encode data in output buffer and prepend the scheme.
    std::string encode_input(static_cast<char*>(output_token.value),
        output_token.length);
    std::string encode_output;
    base::Base64Encode(encode_input, &encode_output);
    *auth_token = scheme_ + " " + encode_output;
    return OK;
}

namespace {

    // GSSAPI status codes consist of a calling error (essentially, a programmer
    // bug), a routine error (defined by the RFC), and supplementary information,
    // all bitwise-or'ed together in different regions of the 32 bit return value.
    // This means a simple switch on the return codes is not sufficient.

    int MapImportNameStatusToError(OM_uint32 major_status)
    {
        VLOG(1) << "import_name returned 0x" << std::hex << major_status;
        if (major_status == GSS_S_COMPLETE)
            return OK;
        if (GSS_CALLING_ERROR(major_status) != 0)
            return ERR_UNEXPECTED;
        OM_uint32 routine_error = GSS_ROUTINE_ERROR(major_status);
        switch (routine_error) {
        case GSS_S_FAILURE:
            // Looking at the MIT Kerberos implementation, this typically is returned
            // when memory allocation fails. However, the API does not guarantee
            // that this is the case, so using ERR_UNEXPECTED rather than
            // ERR_OUT_OF_MEMORY.
            return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
        case GSS_S_BAD_NAME:
        case GSS_S_BAD_NAMETYPE:
            return ERR_MALFORMED_IDENTITY;
        case GSS_S_DEFECTIVE_TOKEN:
            // Not mentioned in the API, but part of code.
            return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
        case GSS_S_BAD_MECH:
            return ERR_UNSUPPORTED_AUTH_SCHEME;
        default:
            return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
        }
    }

    int MapInitSecContextStatusToError(OM_uint32 major_status)
    {
        VLOG(1) << "init_sec_context returned 0x" << std::hex << major_status;
        // Although GSS_S_CONTINUE_NEEDED is an additional bit, it seems like
        // other code just checks if major_status is equivalent to it to indicate
        // that there are no other errors included.
        if (major_status == GSS_S_COMPLETE || major_status == GSS_S_CONTINUE_NEEDED)
            return OK;
        if (GSS_CALLING_ERROR(major_status) != 0)
            return ERR_UNEXPECTED;
        OM_uint32 routine_status = GSS_ROUTINE_ERROR(major_status);
        switch (routine_status) {
        case GSS_S_DEFECTIVE_TOKEN:
            return ERR_INVALID_RESPONSE;
        case GSS_S_DEFECTIVE_CREDENTIAL:
            // Not expected since this implementation uses the default credential.
            return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
        case GSS_S_BAD_SIG:
            // Probably won't happen, but it's a bad response.
            return ERR_INVALID_RESPONSE;
        case GSS_S_NO_CRED:
            return ERR_INVALID_AUTH_CREDENTIALS;
        case GSS_S_CREDENTIALS_EXPIRED:
            return ERR_INVALID_AUTH_CREDENTIALS;
        case GSS_S_BAD_BINDINGS:
            // This only happens with mutual authentication.
            return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
        case GSS_S_NO_CONTEXT:
            return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
        case GSS_S_BAD_NAMETYPE:
            return ERR_UNSUPPORTED_AUTH_SCHEME;
        case GSS_S_BAD_NAME:
            return ERR_UNSUPPORTED_AUTH_SCHEME;
        case GSS_S_BAD_MECH:
            return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
        case GSS_S_FAILURE:
            // This should be an "Unexpected Security Status" according to the
            // GSSAPI documentation, but it's typically used to indicate that
            // credentials are not correctly set up on a user machine, such
            // as a missing credential cache or hitting this after calling
            // kdestroy.
            // TODO(cbentzel): Use minor code for even better mapping?
            return ERR_MISSING_AUTH_CREDENTIALS;
        default:
            if (routine_status != 0)
                return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
            break;
        }
        OM_uint32 supplemental_status = GSS_SUPPLEMENTARY_INFO(major_status);
        // Replays could indicate an attack.
        if (supplemental_status & (GSS_S_DUPLICATE_TOKEN | GSS_S_OLD_TOKEN | GSS_S_UNSEQ_TOKEN | GSS_S_GAP_TOKEN))
            return ERR_INVALID_RESPONSE;

        // At this point, every documented status has been checked.
        return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
    }

}

int HttpAuthGSSAPI::GetNextSecurityToken(const std::string& spn,
    const std::string& channel_bindings,
    gss_buffer_t in_token,
    gss_buffer_t out_token)
{
    // Create a name for the principal
    // TODO(cbentzel): Just do this on the first pass?
    std::string spn_principal = spn;
    gss_buffer_desc spn_buffer = GSS_C_EMPTY_BUFFER;
    spn_buffer.value = const_cast<char*>(spn_principal.c_str());
    spn_buffer.length = spn_principal.size() + 1;
    OM_uint32 minor_status = 0;
    gss_name_t principal_name = GSS_C_NO_NAME;
    OM_uint32 major_status = library_->import_name(
        &minor_status,
        &spn_buffer,
        GSS_C_NT_HOSTBASED_SERVICE,
        &principal_name);
    int rv = MapImportNameStatusToError(major_status);
    if (rv != OK) {
        LOG(ERROR) << "Problem importing name from "
                   << "spn \"" << spn_principal << "\"\n"
                   << DisplayExtendedStatus(library_, major_status, minor_status);
        return rv;
    }
    ScopedName scoped_name(principal_name, library_);

    // Continue creating a security context.
    OM_uint32 req_flags = 0;
    if (can_delegate_)
        req_flags |= GSS_C_DELEG_FLAG;
    major_status = library_->init_sec_context(
        &minor_status, GSS_C_NO_CREDENTIAL, scoped_sec_context_.receive(),
        principal_name, gss_oid_, req_flags, GSS_C_INDEFINITE,
        GSS_C_NO_CHANNEL_BINDINGS, in_token,
        nullptr, // actual_mech_type
        out_token,
        nullptr, // ret flags
        nullptr);
    rv = MapInitSecContextStatusToError(major_status);
    if (rv != OK) {
        LOG(ERROR) << "Problem initializing context. \n"
                   << DisplayExtendedStatus(library_, major_status, minor_status)
                   << '\n'
                   << DescribeContext(library_, scoped_sec_context_.get());
    }
    return rv;
}

} // namespace net
